// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: tianqiang

#include "coin2/exchange/mexc/order_v3/parser.h"

#include <boost/algorithm/string.hpp>

#include "coin/proto/coin_query.pb.h"
#include "coin2/exchange/base/order/order.h"
#include "coin2/exchange/util/json_util.h"
namespace coin2::exchange::mexc::order_v3 {

std::optional<std::string> MexcParser::GetMsg(const JsonValue& data) const {
  if (!data.IsObject() || !data.HasMember("msg") || !data["msg"].IsString()) {
    return std::nullopt;
  }

  return data["msg"].GetString();
}

std::optional<int64_t> MexcParser::GetCode(const JsonValue& data) const {
  if (!data.IsObject() || !data.HasMember("code") || !data["code"].IsString()) {
    return std::nullopt;
  }

  return stoll(data["code"].GetString());
}

bool MexcParser::IsMexcSpotErrMessage(const JsonValue& data) const {
  const auto code = GetCode(data);
  const auto msg = GetMsg(data);

  return (code && code.value() != 200) || (msg && msg.value() != "");
}

auto MexcParser::GetOrderErrorCode(int64_t code) -> OrderErrorCode {
  switch (code) {
    case 200:
      return OrderErrorCode::ORDER_NO_ERROR;
    case 30004:
    case 30005:
      return OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
    case -2011:
      return OrderErrorCode::ORDER_NOT_FOUND;
    default:
      LOG(ERROR) << "[GetOrderErrorCode] " << code;
      return OrderErrorCode::ORDER_UNKNOWN_ERROR;
  }
}

int64_t MexcParser::ParseProcOrderId(const std::string& proc_order_id) const {
  std::vector<std::string> fields = Split(proc_order_id, "_");

  try {
    if (fields.size() == 1) {
      return std::stoll(fields[0]);
    } else if (fields.size() == 2) {
      return std::stoll(fields[1]);
    }
  } catch (std::exception& e) {
    LOG(ERROR) << "Failed to get proc order what:" << e.what() << " id:" << proc_order_id;
  }

  return 0;
}

int64_t MexcParser::GetProcOrderId(const JsonValue& data) const {
  if (!data.HasMember("clientOrderId") || !data["clientOrderId"].IsString()) {
    return 0;
  }

  return ParseProcOrderId(data["clientOrderId"].GetString());
}

int64_t MexcParser::GetWsProcOrderId(const JsonValue& data) const {
  if (!data.HasMember("c") || !data["c"].IsString()) {
    return 0;
  }

  return ParseProcOrderId(data["c"].GetString());
}

auto MexcParser::GetOrderSide(const std::string& side) const -> OrderSide {
  if (side == "BUY") {
    return OrderSide::BUY_ORDER;
  } else if (side == "SELL") {
    return OrderSide::SELL_ORDER;
  } else {
    return OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

auto MexcParser::GetWsOrderSide(const std::string& side) const -> OrderSide {
  if (side == "1") {
    return OrderSide::BUY_ORDER;
  } else if (side == "2") {
    return OrderSide::SELL_ORDER;
  } else {
    return OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

auto MexcParser::GetOrderType(const std::string& type) const -> OrderType {
  if (type == "MARKET") {
    return OrderType::MARKET_ORDER;
  }

  return OrderType::LIMIT_ORDER;
}

auto MexcParser::GetWsOrderType(const std::string& type) const -> OrderType {
  if (type == "5") {
    return OrderType::MARKET_ORDER;
  }

  return OrderType::LIMIT_ORDER;
}

void MexcParser::SetOrderStatus(
    const std::string& status,
    ProductOrderElement* product_order_element) {
  if ((status == "NEW") || (status == "PARTIALLY_FILLED")) {
    product_order_element->set_is_live(true);
  } else if ((status == "CANCELED") || (status == "PARTIALLY_CANCELED")) {
    product_order_element->set_is_live(false);
    product_order_element->set_cancel_confirmed(true);
  } else if (status == "FILLED") {
    product_order_element->set_is_live(false);
    product_order_element->set_fully_filled(true);
  } else {
    LOG(ERROR) << "[SetOrderStatus] Unknown order status. " << status;
  }
}

void MexcParser::SetWsOrderStatus(
    const std::string& status,
    ProductOrderElement* product_order_element) {
  // 订单状态,1:未成交 2:已成交 3:部分成交 4:已撤单 5:部分撤单
  if (status == "1" || status == "3") {
    product_order_element->set_is_live(true);
  } else if (status == "4" || status == "5") {
    product_order_element->set_is_live(false);
    product_order_element->set_cancel_confirmed(true);
  } else if (status == "2") {
    product_order_element->set_is_live(false);
    product_order_element->set_fully_filled(true);
  } else {
    LOG(ERROR) << "[SetWsOrderStatus] Unknown order status. " << status;
  }
}

void MexcParser::ParseRestAccountBalance() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountBalance] Parsing error.\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsMexcSpotErrMessage(document)) {
    LOG(ERROR) << "[ParseRestAccountBalance] Error message.\n" << JsonToString(document);
    return;
  }

  auto account_balance = ParseBalance(document);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
  is_account_balance_initialized_ = true;
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
auto MexcParser::ParseBalance(const JsonValue& doc) -> AccountBalance {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }

  AccountBalance acc_balance;
  acc_balance.set_exchange("Mexc");
  acc_balance.set_market_type("Spot");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      balance->MergeFrom(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }

  return acc_balance;
}

bool MexcParser::ParseRawBalance(const JsonValue& doc, RawBalanceMap* raw_balance_map) {
  /*
  {
    "makerCommission":0,
    "takerCommission":7,
    "buyerCommission":0,
    "sellerCommission":0,
    "canTrade":true,
    "canWithdraw":true,
    "canDeposit":true,
    "updateTime":null,
    "accountType":"SPOT",
    "balances":[
      {
        "asset":"USDT",
        "free":"141.17730633",
        "locked":"0"
      }
    ],
    "permissions":[
      "SPOT"
    ]
  }
  */
  if (!doc.HasMember("balances") || !doc["balances"].IsArray()) {
    LOG(ERROR) << "[ParseRawBalance] Invalid data format.\n" << JsonToString(doc);
    return false;
  }

  for (const auto& balance : doc["balances"].GetArray()) {
    if (!balance.HasMember("asset") || !balance.HasMember("free") || !balance.HasMember("locked")) {
      continue;
    }
    CurrencyBalance currency_balance;
    double avail = stod(balance["free"].GetString());
    double hold = stod(balance["locked"].GetString());
    currency_balance.set_available(avail);
    currency_balance.set_hold(hold);
    currency_balance.set_total(avail + hold);
    raw_balance_map->emplace(balance["asset"].GetString(), currency_balance);
  }

  return true;
}

void MexcParser::ParseRestOrder(const JsonValue& data, ProductOrderElement* product_order_element) {
  /*
  [
    {
      "symbol":"MXUSDT",
      "orderId":"aed0f1f3e90f4ba6ab31d3fce701a644",
      "orderListId":-1,
      "clientOrderId":"debug_123455678",
      "price":"1",
      "origQty":"5",
      "executedQty":"0",
      "cummulativeQuoteQty":"0",
      "status":"NEW",
      "timeInForce":null,
      "type":"LIMIT",
      "side":"BUY",
      "stopPrice":null,
      "icebergQty":null,
      "time":1678868947000,
      "updateTime":1678868947000,
      "isWorking":true,
      "origQuoteOrderQty":"5"
    }
  ]
  */
  product_order_element->set_exchange_type(ExchangeType::Mexc);
  product_order_element->set_market_type(MarketType::Spot);
  product_order_element->set_api_version("v3");
  product_order_element->set_exchange_order_id(data["orderId"].GetString());
  product_order_element->set_symbol(data["symbol"].GetString());
  product_order_element->set_price(stod(data["price"].GetString()));
  product_order_element->set_qty(std::fabs(stod(data["origQty"].GetString())));
  product_order_element->set_proc_order_id(GetProcOrderId(data));
  product_order_element->set_side(GetOrderSide(data["side"].GetString()));
  product_order_element->set_order_type(GetOrderType(data["type"].GetString()));
  product_order_element->set_timestamp(GetCurrentTimestamp());
  SetOrderStatus(data["status"].GetString(), product_order_element);
}

void MexcParser::ParseRestOpenOrderInfo() {
  /*
  [
    {
      "symbol":"MXUSDT",
      "orderId":"aed0f1f3e90f4ba6ab31d3fce701a644",
      "orderListId":-1,
      "clientOrderId":"debug_123455678",
      "price":"1",
      "origQty":"5",
      "executedQty":"0",
      "cummulativeQuoteQty":"0",
      "status":"NEW",
      "timeInForce":null,
      "type":"LIMIT",
      "side":"BUY",
      "stopPrice":null,
      "icebergQty":null,
      "time":1678868947688,
      "updateTime":null,
      "isWorking":true,
      "origQuoteOrderQty":"5"
    }
  ]
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseRestOpenOrderInfo] Parsing error.\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsMexcSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseRestOpenOrderInfo] Error message.\n" << JsonToString(doc);
    return;
  }

  if (!doc.IsArray()) {
    LOG(ERROR) << "[ParseRestOpenOrderInfo] Invalid data format.\n" << JsonToString(doc);
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  ProductOrderBundle* product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : doc.GetArray()) {
    if (!data.HasMember("symbol")) {
      continue;
    }
    std::string native_symbol = data["symbol"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto order = product_order_bundle->add_each_order();
    order->Clear();
    ParseRestOrder(data, order);
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }

  // CheckZombieOrder(product_order_bundle);
}

void MexcParser::ParseRestAllOrderInfo() {
  /*
  [
    {
      "symbol":"MXUSDT",
      "orderId":"aed0f1f3e90f4ba6ab31d3fce701a644",
      "orderListId":-1,
      "clientOrderId":"debug_123455678",
      "price":"1",
      "origQty":"5",
      "executedQty":"0",
      "cummulativeQuoteQty":"0",
      "status":"NEW",
      "timeInForce":null,
      "type":"LIMIT",
      "side":"BUY",
      "stopPrice":null,
      "icebergQty":null,
      "time":1678868947000,
      "updateTime":1678868947000,
      "isWorking":true,
      "origQuoteOrderQty":"5"
    }
  ]
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseRestAllOrderInfo] Parsing error.\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsMexcSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseRestAllOrderInfo] Error message.\n" << JsonToString(doc);
    return;
  }

  if (!doc.IsArray()) {
    LOG(ERROR) << "[ParseRestAllOrderInfo] Invalid data format.\n" << JsonToString(doc);
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  // LoadZombieOrderCheckList(msg_->http_context->extra_info);

  ProductOrderBundle* product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : doc.GetArray()) {
    auto order = product_order_bundle->add_each_order();
    order->Clear();
    ParseRestOrder(data, order);
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }

  // CheckZombieOrder(product_order_bundle);
}

// bool MexcParser::LoadZombieOrderCheckList(const std::string& mixin_order_ids) {
//   if (mixin_order_ids.empty()) {
//     return false;
//   }

//   // workaround.
//   if (mixin_order_ids[0] == 'M') {
//     return false;
//   }

//   mixin_zombie_order_ids_map_.clear();
//   std::vector<std::string> vec;
//   boost::split(vec, mixin_order_ids, boost::is_any_of(","));
//   for (const auto& ele : vec) {
//     if (ele.find("@") == std::string::npos) {
//       continue;
//     }
//     std::vector<std::string> ids;
//     boost::split(ids, ele, boost::is_any_of("@"));
//     if ((ids.size() != 2) || ids[0].empty() || ids[1].empty()) {
//       continue;
//     }
//     const auto proc_order_id = stoll(ids[0]);
//     const auto& exchange_order_id = ids[1];
//     mixin_zombie_order_ids_map_[exchange_order_id].push_back(proc_order_id);
//   }

//   return mixin_zombie_order_ids_map_.size() > 0;
// }

// void MexcParser::CheckZombieOrder(ProductOrderBundle* product_order_bundle) {
//   if (product_order_bundle->each_order_size() == 0) {
//     return;
//   }
//   if (mixin_zombie_order_ids_map_.size() == 0) {
//     return;
//   }

//   std::vector<std::pair<std::string, int64_t>> mixin_order_ids;
//   for (const auto& order_info : product_order_bundle->each_order()) {
//     mixin_order_ids.push_back(
//         std::make_pair(order_info.exchange_order_id(), order_info.proc_order_id()));
//   }

//   // set dead for the order which proc_order_id is difference, which means the order is amended
//   for (const auto& [exchange_order_id, client_order_id] : mixin_order_ids) {
//     if (mixin_zombie_order_ids_map_.count(exchange_order_id) == 0) {
//       continue;
//     }
//     auto& proc_order_ids = mixin_zombie_order_ids_map_.at(exchange_order_id);
//     auto iter = proc_order_ids.begin();
//     while (iter != proc_order_ids.end()) {
//       auto proc_order_id = *iter;
//       iter = proc_order_ids.erase(iter);
//       if (proc_order_id == client_order_id) {
//         continue;
//       }
//       auto* oc = ocm_->FindByProcOrderId(proc_order_id);
//       if (!oc) {
//         continue;
//       }
//       if (!ocm_->IsInWorkingOrders(oc)) {
//         continue;
//       }
//       LOG(INFO) << "set zombie order dead " << proc_order_id << "@" << oc->exchange_order_id();
//       auto* order = product_order_bundle->add_each_order();
//       order->set_market_type(MarketType::Spot);
//       order->set_exchange_type(ExchangeType::Mexc);
//       order->set_api_version("v2");
//       order->set_exchange_order_id(oc->exchange_order_id());
//       order->set_symbol(oc->order_spec().product);
//       order->set_price(oc->order_spec().price);
//       order->set_qty(oc->order_spec().qty);
//       order->set_proc_order_id(oc->proc_order_id());
//       order->set_side(oc->order_spec().order_side);
//       order->set_order_type(oc->order_spec().order_type);
//       order->set_timestamp(msg_->timestamp);
//       order->set_cancel_confirmed(true);
//     }
//     mixin_zombie_order_ids_map_.erase(exchange_order_id);
//   }
// }

void MexcParser::ParseRestFill(const JsonValue& data, ProductFillElement* product_fill_element) {
  /*
  [
    {
      "symbol":"MXUSDT",
      "id":"de3580f59726428bb5593ad19ecb539c",
      "orderId":"b6254aa5b7e043659b32daf838604fb9",
      "orderListId":-1,
      "price":"1.865",
      "qty":"3",
      "quoteQty":"5.595",
      "commission":"0.0039165",
      "commissionAsset":"USDT",
      "time":1678870859000,
      "isBuyer":true,
      "isMaker":false,
      "isBestMatch":true,
      "isSelfTrade":false,
      "clientOrderId":"debug_123455680"
    }
  ]
  */
  product_fill_element->set_symbol(data["symbol"].GetString());
  product_fill_element->set_fill_timestamp(GetCurrentTimestamp());
  product_fill_element->set_exchange_order_id(data["orderId"].GetString());
  product_fill_element->set_price(stod(data["price"].GetString()));
  product_fill_element->set_qty(std::fabs(stod(data["qty"].GetString())));
  product_fill_element->set_side(
      data["isBuyer"].GetBool() ? OrderSide::BUY_ORDER : OrderSide::SELL_ORDER);
  product_fill_element->set_fill_id(data["id"].GetString());
  product_fill_element->set_fee(stod(data["commission"].GetString()));
  product_fill_element->set_fee_currency(data["commissionAsset"].GetString());
  product_fill_element->set_fill_type(
      data["isMaker"].GetBool() ? FillType::MAKER_FILL_TYPE : FillType::TAKER_FILL_TYPE);
}

void MexcParser::ParseRestFillOrderInfo() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseRestFillOrderInfo] Parsing error.\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsMexcSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseRestFillOrderInfo] Error message.\n" << JsonToString(doc);
    return;
  }

  if (!doc.IsArray()) {
    LOG(ERROR) << "[ParseRestFillOrderInfo] Invalid data format.\n" << JsonToString(doc);
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  ProductFillBundle* product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : doc.GetArray()) {
    // daniel(20220706): ignore the fills long ago
    int64_t fill_timestamp_et = stoll(data["time"].GetString()) * 1e6;
    int64_t fill_timestamp_ft = msg_->timestamp;
    if (fill_timestamp_ft - fill_timestamp_et > 100 * 1e9) {
      continue;
    }
    auto fill = product_fill_bundle->add_each_fill();
    fill->Clear();
    ParseRestFill(data, fill);
    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void MexcParser::ParseListenKey() {
  /*
  {
    "listenKey":"pqia91ma19a5s61cv6a81va65sdf19v8a65a1a5s61cv6a81va65sdf19v8a65a1"
  }
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseListenKey] Parsing error.\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsMexcSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseListenKey] Error message.\n" << JsonToString(doc);
    return;
  }

  if (!doc.HasMember("listenKey")) {
    LOG(ERROR) << "[ParseListenKey] Invalid data format.\n" << JsonToString(doc);
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  std::string listen_key(doc["listenKey"].GetString());
  mb_->PublishEvent("mexc/listen_key", &listen_key);
}

void MexcParser::ParseSubmitResponse() {
  /*
  {
    "symbol":"MXUSDT",
    "orderId":"aed0f1f3e90f4ba6ab31d3fce701a644",
    "orderListId":-1,
    "clientOrderId":"debug_123455678",
    "price":"1",
    "origQty":"5",
    "type":"LIMIT",
    "side":"BUY",
    "transactTime":1678868947688
  }
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_timestamp(GetCurrentTimestamp());
  resp->set_success(false);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseSubmitResponse] AsioHttpContextStatus error";
    resp->set_error_code(OrderErrorCode::ORDER_NETWORK_ERROR);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseSubmitResponse] Parsing error.\n" << msg_->DebugString();
    resp->set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  const auto code = GetCode(json);
  const auto msg = GetMsg(json);

  if (code && msg) {
    auto order_error_code = GetOrderErrorCode(code.value());
    resp->set_error_code(order_error_code);
    resp->set_exchange_error_code(std::to_string(code.value()));
    resp->set_exchange_error_msg(msg.value());

    if (order_error_code != OrderErrorCode::ORDER_NO_ERROR) {
      LOG(ERROR) << "[ParseSubmitResponse] Error message.\n" << JsonToString(json);
      return;
    }
  }

  if (!json.HasMember("orderId") || !json["orderId"].IsString()) {
    resp->set_error_code(OrderErrorCode::INVALID_JSON);
    LOG(ERROR) << "[ParseSubmitResponse] Invalid data format.\n" << JsonToString(json);
    return;
  }

  resp->set_exchange_order_id(json["orderId"].GetString());
  resp->set_success(true);
}

void MexcParser::ParseBatchSubmitResponse() {
  /*
  [
      {
          "symbol":"MXUSDT",
          "orderId":"63b05c62b9ce4cd6aee5d53f5e158dc0",
          "orderListId":-1,
          "clientOrderId":"58747_1687847797307261138",
          "transactTime":1687849417043
      },
      {
          "symbol":"MXUSDT",
          "orderId":"9cb82da757ea4fd7bde96ae83d43000b",
          "orderListId":-1,
          "clientOrderId":"58747_1687847797307261139",
          "transactTime":1687849417048
      },
      {
          "symbol":"MXUSDT",
          "orderId":"5ca0b45dc2e646ec895bc4c895ba31e8",
          "orderListId":-1,
          "clientOrderId":"58747_1687847797307261140",
          "transactTime":1687849417053
      }
  ]
  */
  const auto& oc_list = msg_->oc_list;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::MULTI_SUBMIT_RESPONSE);
  auto* response = result_.mutable_multi_submit_response();
  response->Clear();

  std::unordered_map<int64_t, SubmitOrderResponse*> proc_order_id_to_response_map;
  for (const auto* order_context : oc_list) {
    auto* resp = response->add_each_submit_response();

    resp->set_symbol(order_context->order_spec().product);
    resp->set_proc_order_id(order_context->proc_order_id());
    resp->set_http_status(http_context->res.result_int());
    resp->set_timestamp(GetCurrentTimestamp());
    resp->set_success(false);

    CHECK(proc_order_id_to_response_map.count(order_context->proc_order_id()) == 0)
        << "proc_order_id duplicate proc_order_id:" << order_context->proc_order_id();
    proc_order_id_to_response_map[order_context->proc_order_id()] = resp;
  }

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[" << __func__ << "] AsioHttpContextStatus Error";
    for (auto& resp : *(response->mutable_each_submit_response())) {
      resp.set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    }
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json) || !json.IsArray()) {
    LOG(ERROR) << "[" << __func__ << "] Parse Error\n" << http_context->res;
    for (auto& resp : *(response->mutable_each_submit_response())) {
      resp.set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    }
    return;
  }

  const auto& array = json.GetArray();

  CHECK_EQ(proc_order_id_to_response_map.size(), array.Size())
      << "Batch submit response size not equal to request size response:" << JsonToString(json)
      << " request:" << response->DebugString();

  for (const auto& data : array) {
    int64_t client_order_id = GetProcOrderId(data);

    CHECK(proc_order_id_to_response_map.count(client_order_id) != 0)
        << "unknown client order id found client_order_id:" << client_order_id;

    auto* resp = proc_order_id_to_response_map[client_order_id];

    const auto code = GetCode(data);
    const auto msg = GetMsg(data);

    if (code && msg) {
      auto order_error_code = GetOrderErrorCode(code.value());
      resp->set_error_code(order_error_code);
      resp->set_exchange_error_code(std::to_string(code.value()));
      resp->set_exchange_error_msg(msg.value());

      if (order_error_code != OrderErrorCode::ORDER_NO_ERROR) {
        LOG(ERROR) << "[" << __func__ << "] Error message.\n" << JsonToString(data);
        continue;
      }
    }

    if (!data.HasMember("orderId") || !data["orderId"].IsString()) {
      resp->set_error_code(OrderErrorCode::INVALID_JSON);
      LOG(ERROR) << "[" << __func__ << "] Invalid data format.\n" << JsonToString(data);
      continue;
    }

    resp->set_exchange_order_id(data["orderId"].GetString());
    resp->set_success(true);
  }
}

void MexcParser::ParseCancelResponse() {
  /*
  {
    "symbol":"MXUSDT",
    "origClientOrderId":"debug_123455679",
    "orderId":"1798ed8b18044368b7bf3564ed524159",
    "price":"1.86",
    "origQty":"3",
    "type":"LIMIT",
    "side":"BUY",
    "executedQty":"0",
    "cummulativeQuoteQty":"0",
    "status":"NEW"
  }
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(GetCurrentTimestamp());
  resp->set_success(false);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseCancelResponse] AsioHttpContextStatus error";
    resp->set_error_code(OrderErrorCode::ORDER_NETWORK_ERROR);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseCancelResponse] Parsing error.\n" << msg_->DebugString();
    resp->set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  const auto code = GetCode(json);
  const auto msg = GetMsg(json);

  if (code && msg) {
    auto order_error_code = GetOrderErrorCode(code.value());
    resp->set_error_code(order_error_code);
    resp->set_exchange_error_code(std::to_string(code.value()));
    resp->set_exchange_error_msg(msg.value());

    if (order_error_code != OrderErrorCode::ORDER_NO_ERROR) {
      LOG(ERROR) << "[ParseCancelResponse] Error message.\n" << JsonToString(json);
      return;
    }
  }

  if (!json.HasMember("status") || !json["status"].IsString()) {
    resp->set_error_code(OrderErrorCode::INVALID_JSON);
    LOG(ERROR) << "[ParseCancelResponse] Invalid data format.\n" << JsonToString(json);
    return;
  }

  std::string status(json["status"].GetString());
  if (status == "CANCELED" || status == "PARTIALLY_CANCELED") {
    resp->set_cancel_confirmed(true);
  }

  resp->set_success(true);
}

void MexcParser::ParseWsOrderInfo(const JsonDocument& document) {
  /*
  {
    "c":"spot@private.orders.v3.api",
    "d":{
      "i":"d183822070fa4136a611fbd8cc9bcdc7",
      "c":"33899_1678934988520801665",
      "o":2,
      "p":1.8685,
      "v":3,
      "S":1,
      "a":5.6055,
      "m":0,
      "A":5.6055,
      "V":3,
      "s":1,
      "O":1678935000713,
      "ap":0,
      "cv":0,
      "ca":0
    },
    "s":"MXUSDT",
    "t":1678935000725
  }
  */
  if (!document.HasMember("s") || !document.HasMember("d")) {
    LOG(ERROR) << "[ParseWsOrderInfo] Invalid data format.\n" << JsonToString(document);
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  auto order = product_order_bundle->add_each_order();
  auto& data = document["d"];
  order->set_exchange_type(ExchangeType::Mexc);
  order->set_market_type(MarketType::Spot);
  order->set_api_version("v3");
  order->set_exchange_order_id(data["i"].GetString());
  order->set_symbol(document["s"].GetString());
  order->set_price(stod(data["p"].GetString()));
  order->set_qty(std::fabs(stod(data["v"].GetString())));
  order->set_proc_order_id(GetWsProcOrderId(data));
  order->set_side(GetWsOrderSide(data["S"].GetString()));
  order->set_order_type(GetWsOrderType(data["o"].GetString()));
  order->set_timestamp(GetCurrentTimestamp());
  SetWsOrderStatus(data["s"].GetString(), order);

  result_.set_type(MsgType::ORDER_STATUS);
}

void MexcParser::ParseWsDealsInfo(const JsonDocument& document) {
  /*
  {
    "c":"spot@private.deals.v3.api",
    "d":{
      "p":"1.8691",
      "v":"3",
      "a":"5.6073",
      "S":2,
      "T":1678935055410,
      "t":"b9dd841cec524808a82f8586bdd3991e",
      "c":"54742_1678934988520801686",
      "i":"d2f18b0683a34094a77686a3d7fedf8c",
      "m":1,
      "st":0,
      "n":"0",
      "N":"USDT"
    },
    "s":"MXUSDT",
    "t":1678935055422
  }
  */
  if (!document.HasMember("s") || !document.HasMember("d")) {
    LOG(ERROR) << "[ParseWsDealsInfo] Invalid data format.\n" << JsonToString(document);
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  ProductFillBundle* product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  auto fill = product_fill_bundle->add_each_fill();
  auto& data = document["d"];
  fill->set_symbol(document["s"].GetString());
  fill->set_fill_timestamp(GetCurrentTimestamp());
  fill->set_proc_order_id(GetWsProcOrderId(data));
  fill->set_exchange_order_id(data["i"].GetString());
  fill->set_price(stod(data["p"].GetString()));
  fill->set_qty(std::fabs(stod(data["v"].GetString())));
  fill->set_side(GetWsOrderSide(data["S"].GetString()));
  fill->set_fill_id(data["t"].GetString());
  fill->set_fill_type(
      data["m"].GetString()[0] == '1' ? FillType::MAKER_FILL_TYPE : FillType::TAKER_FILL_TYPE);

  result_.set_type(MsgType::ORDER_FILL);
}

void MexcParser::ParseWsBalanceInfo(const JsonDocument& document) {
  /*
  {
    "c":"spot@private.account.v3.api",
    "d":{
      "a":"MX",
      "c":1678935055420,
      "f":"3",
      "fd":"0",
      "l":"0",
      "ld":"-3",
      "o":"ENTRUST"
    },
    "t":1678935055422
  }
  */
  if (!is_account_balance_initialized_) {
    return;
  }

  if (!document.HasMember("d")) {
    LOG(ERROR) << "[ParseWsBalanceInfo] Invalid data format\n" << JsonToString(document);
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  const auto& data = document["d"];
  if (!data.HasMember("a") || !data.HasMember("f") || !data.HasMember("l")) {
    LOG(ERROR) << "[ParseWsBalanceInfo] Invalid data format\n" << JsonToString(document);
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (!symcache_->has_currency_native(data["a"].GetString())) {
    // ignore
    result_.set_type(MsgType::IGNORABLE);
    return;
  }
  auto acc_balance = result_.mutable_account_balance();
  acc_balance->Clear();
  acc_balance->set_exchange("Mexc");
  acc_balance->set_market_type("Spot");

  auto* currency = symcache_->GetCurrencyByNative(data["a"].GetString());
  auto* balance = acc_balance->add_each_balance();
  balance->set_currency(currency->currency());
  balance->set_currency_native(currency->native_currency());

  double avail = stod(data["f"].GetString());
  double hold = stod(data["l"].GetString());
  balance->set_available(avail);
  balance->set_hold(hold);
  balance->set_total(avail + hold);

  result_.set_type(MsgType::ACCOUNT_BALANCE);
}

void MexcParser::ParseWsMessage() {
  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  DLOG(INFO) << "OG ws msg: " << JsonToString(json);

  if (json.HasMember("msg") && json["msg"].IsString()) {
    const std::string msg(json["msg"].GetString());

    LOG(INFO) << "ws msg:" << msg;
    return;
  }

  if (!json.HasMember("c") || !json["c"].IsString()) {
    LOG(ERROR) << "[ParseWsMessage] Invalid data format. " << JsonToString(json);
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  const std::string channel(json["c"].GetString());

  if (channel == "spot@private.orders.v3.api") {
    ParseWsOrderInfo(json);
  } else if (channel == "spot@private.deals.v3.api") {
    ParseWsDealsInfo(json);
  } else if (channel == "spot@private.account.v3.api") {
    ParseWsBalanceInfo(json);
  }
}

void MexcParser::ParseRestDeposit() {
  /*
  [
    {
      "amount":"50000",
      "coin":"EOS",
      "network":"EOS",
      "status":5,
      "address":"0x20b7cf77db93d6ef1ab979c49142ec168427fdee",
      "txId":"01391d1c1397ef0a3cbb3c7f99a90846f7c8c2a8dddcdcf84f46b530dede203e1bc804",
      "insertTime":1659513342000,
      "unlockConfirm":"10",
      "confirmTimes":"241",
      "memo":"xxyy1122"
    }
  ]
  */
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestDeposit] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (!document.IsArray()) {
    LOG(ERROR) << "[ParseRestDeposit] Invalid data format\n" << JsonToString(document);
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto currency_transfer_bundle = result_.mutable_currency_transfer_bundle();
  currency_transfer_bundle->Clear();
  bool currency_transfer_bundle_found = false;
  for (const auto& data : document.GetArray()) {
    const static std::vector<std::string> fields =
        {"coin", "amount", "insertTime", "txId", "address", "status"};
    if (!JsonFieldCheck(data, fields)) {
      LOG(ERROR) << "[ParseRestDeposit] Invalid Withdraw format" << JsonToString(data);
      continue;
    }

    std::string currency_str;
    std::vector<std::string> splitted;
    splitted = std::move(Split(data["coin"].GetString(), "-"));
    if (splitted.size() == 1 || splitted.size() == 2) {
      currency_str = std::move(splitted[0]);
    } else {
      LOG(ERROR) << "[ParseRestDeposit] Invalid Deposit currency format" << JsonToString(data);
      continue;
    }

    // 1:SMALL,2:TIME_DELAY,3:LARGE_DELAY,
    // 4:PENDING,5:SUCCESS,6:AUDITING,7:REJECTED
    if (data["txId"].IsNull()) {
      continue;
    }

    std::string transfer_id = data["txId"].GetString();
    std::string status = data["status"].GetString();

    auto proto_status = TransferRequestProto::TRANSFER_REJECTED;
    if (status == "7") {
      proto_status = TransferRequestProto::TRANSFER_REJECTED;
    } else if (status == "5") {
      proto_status = TransferRequestProto::TRANSFER_CONFIRMED;
    } else if (status == "1" || status == "2" || status == "3" || status == "4" || status == "6") {
      proto_status = TransferRequestProto::TRANSFER_ACCEPTED;
    } else {
      LOG(ERROR) << "[ParseRestDeposit] Unknown Withdraw status \n" << JsonToString(data);
      continue;
    }

    if (!transfer_first_record_) {
      if (transfer_deposit_map_[transfer_id] == proto_status) {
        continue;  // Filtering Duplicates
      }
    }
    transfer_deposit_map_.insert_or_assign(transfer_id, proto_status);

    int64_t create_timestamp = stoll(data["insertTime"].GetString()) * 1e6;
    auto currency = MexcCurrency::FromNativeStrImpl(currency_str, create_timestamp);
    auto* currency_transfer = currency_transfer_bundle->add_each_transfer();
    currency_transfer->set_type(TransferRequestProto::DEPOSIT);
    currency_transfer->set_from_address(data["address"].GetString());
    currency_transfer->set_status(proto_status);
    currency_transfer->set_currency(currency->currency());
    currency_transfer->set_external_transfer_id(transfer_id);
    currency_transfer->set_create_timestamp(create_timestamp);
    currency_transfer->set_qty(stod(data["amount"].GetString()));
    currency_transfer_bundle_found = true;
  }

  if (!transfer_first_record_) {
    if (!is_transfer_deposit_initialized_) {
      is_transfer_deposit_initialized_ = true;
      LOG(INFO) << "[ParseRestDeposit] Withdrawal ignored: " << transfer_deposit_map_.size();
      return;
    }
  }

  if (currency_transfer_bundle_found) {
    LOG(INFO) << "[ParseRestDeposit] Withdrawal parsed: "
              << currency_transfer_bundle->each_transfer_size();
    result_.set_type(MsgType::TRANSFER);
  }
}

void MexcParser::ParseRestWithdrawal() {
  /*
  [
    {
      "id":"bb17a2d452684f00a523c015d512a341",
      "txId":null,
      "coin":"EOS",
      "network":"EOS",
      "address":"zzqqqqqqqqqq",
      "amount":"10",
      "transferType":0,
      "status":3,
      "transactionFee":"0",
      "confirmNo":null,
      "applyTime":1665300874000,
      "remark":"",
      "memo":"MX10086"
    }
  ]
  */
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestWithdrawal] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (!document.IsArray()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    LOG(ERROR) << "[ParseRestWithdrawal] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto currency_transfer_bundle = result_.mutable_currency_transfer_bundle();
  currency_transfer_bundle->Clear();
  bool currency_transfer_bundle_found = false;
  for (const auto& data : document.GetArray()) {
    const static std::vector<std::string> fields =
        {"coin", "amount", "applyTime", "txId", "address", "status", "id"};
    if (!JsonFieldCheck(data, fields)) {
      LOG(ERROR) << "[ParseRestWithdrawal] Invalid Withdraw format" << JsonToString(data);
      continue;
    }

    std::string currency_str;
    std::vector<std::string> splitted;
    splitted = std::move(Split(data["coin"].GetString(), "-"));
    if (splitted.size() == 1 || splitted.size() == 2) {
      currency_str = std::move(splitted[0]);
    } else {
      LOG(ERROR) << "[ParseRestWithdrawal] Invalid Deposit currency format" << JsonToString(data);
      continue;
    }

    // status,1:APPLY,2:AUDITING,3:WAIT,4:PROCESSING,5:WAIT_PACKAGING,
    // 6:WAIT_CONFIRM,7:SUCCESS,8:FAILED,9:CANCEL,10:MANUAL

    std::string id = data["id"].GetString();
    std::string status = data["status"].GetString();
    auto proto_status = TransferRequestProto::TRANSFER_REJECTED;
    if (status == "8" || status == "9") {
      proto_status = TransferRequestProto::TRANSFER_REJECTED;
    } else if (status == "7") {
      proto_status = TransferRequestProto::TRANSFER_CONFIRMED;
    } else if (
        status == "1" || status == "2" || status == "3" || status == "4" || status == "5" ||
        status == "6" || status == "10") {
      proto_status = TransferRequestProto::TRANSFER_ACCEPTED;
    } else {
      LOG(ERROR) << "[ParseRestWithdrawal] Unknown Withdraw status \n" << JsonToString(data);
      continue;
    }

    if (!transfer_first_record_) {
      if (transfer_withdrawal_map_[id] == proto_status) {
        continue;  // Filtering Duplicates
      }
    }
    transfer_withdrawal_map_.insert_or_assign(id, proto_status);

    int64_t create_timestamp = stoll(data["applyTime"].GetString()) * 1e6;
    auto currency = MexcCurrency::FromNativeStrImpl(currency_str, create_timestamp);
    auto* currency_transfer = currency_transfer_bundle->add_each_transfer();
    currency_transfer->set_type(TransferRequestProto::WITHDRAW);
    currency_transfer->set_currency(currency->currency());
    currency_transfer->set_create_timestamp(create_timestamp);
    currency_transfer->set_qty(stod(data["amount"].GetString()));
    if (data["address"].IsString()) {
      currency_transfer->set_to_address(data["address"].GetString());
    }
    currency_transfer->set_status(proto_status);
    if (data["txId"].IsString()) {
      currency_transfer->set_external_transfer_id(data["txId"].GetString());
    }
    currency_transfer_bundle_found = true;
  }

  if (!transfer_first_record_) {
    if (!is_transfer_withdrawal_initialized_) {
      is_transfer_withdrawal_initialized_ = true;
      LOG(INFO) << "[ParseRestWithdrawal] Withdrawal ignored: " << transfer_withdrawal_map_.size();
      return;
    }
  }

  if (currency_transfer_bundle_found) {
    LOG(INFO) << "[ParseRestWithdrawal] Withdrawal parsed: "
              << currency_transfer_bundle->each_transfer_size();
    result_.set_type(MsgType::TRANSFER);
  }
}

void MexcParser::ParseTransfer(std::string account_info) {
  /*
  {
    "result":[
      {
        "tranId":"11945860693",
        "fromAccount":"master@test.com",
        "toAccount":"subaccount1@test.com",
        "clientTranId":"test",
        "asset":"BTC",
        "amount":"0.1",
        "fromAccountType":"SPOT",
        "toAccountType":"FUTURE",
        "fromSymbol":"SPOT",
        "toSymbol":"FUTURE",
        "status":"SUCCESS",
        "timestamp":1544433325000
      }
    ],
    "totalCount":0
  }
  */
  static std::vector<std::string> main_account_mail_list = {"prestoinvtbvi@gmail.com"};

  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseTransfer] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (!document.HasMember("result") || !document["result"].IsArray()) {
    LOG(ERROR) << "[ParseTransfer] Invalid data format\n" << JsonToString(document);
    result_.set_error_code(OrderErrorCode::INVALID_MSG_TYPE);
    return;
  }
  auto currency_transfer_bundle = result_.mutable_currency_transfer_bundle();
  currency_transfer_bundle->Clear();
  bool currency_transfer_bundle_found = false;
  for (const auto& data : document["result"].GetArray()) {
    const static std::vector<std::string> fields =
        {"tranId", "fromAccount", "toAccount", "asset", "amount", "status", "timestamp"};
    if (!JsonFieldCheck(data, fields)) {
      LOG(ERROR) << "[ParseTransfer] Invalid Withdraw format" << JsonToString(data);
      continue;
    }

    int64_t create_time = stoll(data["timestamp"].GetString()) * 1e6;
    auto currency = MexcCurrency::FromNativeStrImpl(data["asset"].GetString(), create_time);
    std::string status = data["status"].GetString();
    auto proto_status = TransferRequestProto::TRANSFER_REJECTED;
    if (status == "FAILED") {
      proto_status = TransferRequestProto::TRANSFER_REJECTED;
    } else if (status == "SUCCESS") {
      proto_status = TransferRequestProto::TRANSFER_CONFIRMED;
    } else if (status == "MANUAL" || status == "APPLY" || status == "AUDITING") {
      proto_status = TransferRequestProto::TRANSFER_ACCEPTED;
    } else {
      LOG(ERROR) << "[ParseTransfer] Unknown status \n" << JsonToString(data);
      continue;
    }

    std::string transfer_id = data["tranId"].GetString();

    if (!transfer_first_record_) {
      if (transfer_id_map_[transfer_id] == proto_status) {
        continue;  // Filtering Duplicates
      }
    }

    transfer_id_map_.insert_or_assign(transfer_id, proto_status);

    auto proto_type = TransferRequestProto::WITHDRAW_SUBACCOUNT;
    auto ret = std::any_of(
        main_account_mail_list.begin(),
        main_account_mail_list.end(),
        [&data](const std::string& str) { return str == data["fromAccount"].GetString(); });
    if (!ret) {
      proto_type = TransferRequestProto::DEPOSIT_SUBACCOUNT;
    }

    auto* currency_transfer = currency_transfer_bundle->add_each_transfer();
    currency_transfer->set_type(proto_type);
    currency_transfer->set_currency(currency->currency());
    currency_transfer->set_status(proto_status);
    currency_transfer->set_qty(stod(data["amount"].GetString()));
    currency_transfer->set_from_address(data["fromAccount"].GetString());
    currency_transfer->set_to_address(data["toAccount"].GetString());
    currency_transfer->set_create_timestamp(create_time);
    currency_transfer->set_external_transfer_id(transfer_id);
    currency_transfer_bundle_found = true;
  }

  if (!transfer_first_record_) {
    if (!transfer_account_info_set_.count(account_info)) {
      transfer_account_info_set_.insert(account_info);
      LOG(INFO) << "[ParseTransfer] Withdrawal ignored: " << transfer_deposit_map_.size();
      return;
    }
  }

  if (currency_transfer_bundle_found) {
    LOG(INFO) << "[ParseTransfer] Withdrawal parsed: "
              << currency_transfer_bundle->each_transfer_size();
    result_.set_type(MsgType::TRANSFER);
  }
}

int32_t MexcParser::ParseErrorMessage(const std::shared_ptr<AsioHttpContext>& http_context) {
  const auto http_code = http_context->res.result_int();
  if (http_code == 401) {
    LOG(ERROR) << "[ParseErrorMessage] Unauthorized-Authentication failed\n" << msg_->DebugString();
  } else if (http_code == 403) {
    LOG(ERROR) << "[ParseErrorMessage] Access denied\n" << msg_->DebugString();
  } else if (http_code == 404) {
    LOG(ERROR) << "[ParseErrorMessage] Request path not found\n" << msg_->DebugString();
  } else if (http_code == 429) {
    auto enable_time = 5 * 1'000'000'000LL + GetCurrentTimestamp();
    mb_->PublishEvent("mexc/enable_time", reinterpret_cast<void*>(enable_time));
    LOG(ERROR) << "[ParseErrorMessage] Rate Limit Exceeded\n" << msg_->DebugString();
  } else if (http_code == 500) {
    LOG(WARNING) << "[ParseErrorMessage] Mexc service problem\n" << msg_->DebugString();
  }

  return http_code;
}

auto MexcParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg.type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  ParseErrorMessage(msg.http_context);

  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::MULTI_SUBMIT_RESPONSE:
      ParseBatchSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
      ParseRestOpenOrderInfo();
      break;
    case OrderSubTopicId::REST_ALL_ORDERS:
      ParseRestAllOrderInfo();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseRestFillOrderInfo();
      break;
    case OrderSubTopicId::REST_ACCOUNT_BALANCE:
      ParseRestAccountBalance();
      break;
    case OrderSubTopicId::LISTEN_KEY:
      ParseListenKey();
      break;
    case MexcOrderSubTopicId::DEPOSIT_SUBACCOUNT:
      ParseTransfer(msg.http_context->extra_info);
      break;
    case MexcOrderSubTopicId::REST_DEPOSIT:
      ParseRestDeposit();
      break;
    case MexcOrderSubTopicId::REST_WITHDRAWAL:
      ParseRestWithdrawal();
      break;
    default: {
      LOG(ERROR) << "Parsing Error. " << msg_->DebugString();
      result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
      break;
    }
  }

  return result_;
}

}  // namespace coin2::exchange::mexc::order_v3
